import { vnode } from ".";

export function patch(oldVnode, vnode) {
    if (!oldVnode) { // 组件挂载时，初始化没有传递el
        return createElem(vnode);
    }
    // 初始化
    if (oldVnode.nodeType) {
        let el = createElem(vnode);
        oldVnode.parentElement.insertBefore(el, oldVnode.nextElementSibling);
        oldVnode.parentElement.removeChild(oldVnode);
        return el;
    } else {
        // diff操作
        // 1 父亲的标签一不一样 直接使用新的替换旧的
        if (vnode.tag !== oldVnode.tag) {
            return oldVnode.el.parentElement.replaceChild(createElem(vnode), oldVnode.el);
        }
        // 如果tag都一样，并且不是文本，重用el
        let el = vnode.el = oldVnode.el;
        // <div>a</div> <div>b</div>
        // 2 子元素都是文本 到这说明tag(string|undefined)一样的
        if (!oldVnode.tag) { // 是文本
            // return el.innerText = vnode.text;
            if (oldVnode.text !== vnode.text) {
                // 通过document.createElement创建的只有el只有textContent, innerText无效
                return oldVnode.el.textContent = vnode.text;
            }
        }




        // 3 更新属性
        updateProperty(vnode, oldVnode.attrs);

        // 4更新孩子
        // 1) 老的孩子有，新的孩子有； 2) 老的没有，新的有；  3)老的有，新的没有
        const oldChildren = oldVnode.children || [];
        const children = vnode.children || [];
        if (oldChildren.length > 0 && children.length > 0) {
            // 新老都有孩子
            // diff原理
            updateChildren(oldChildren, children, el);
        } else if (children.length > 0) {
            // 新的有孩子,老的没有
            for (let i = 0; i < children.length; i++) {
                let child = children[i];
                el.appendChild(createElem(child));
            }
        } else if (oldChildren.length > 0) {
            // 老的有孩子, 新的没有
            el.innerHTML = '';
        }


    }
}


// function createEle(vnode) {
//     // 标签类型
//     if (vnode.tag) {
//         vnode.el = document.createElement(vnode.tag);
//         updateProperty(vnode);
//         children.forEach(child => {
//             vnode.el.appendChild(createEle(child));
//         })
//     } else { // 文本类型也有el
//         vnode.el = document.createTextNode(vnode.text);
//     }

//     return vnode.el;
// }

// 比较两个虚拟节点是否一样
function isSameVnode(n1, n2) {
    // 一般要加key，如果不加，那么标签名一样就算是同一个元素,这样比较会费较大的性能
    return (n1.tag === n2.tag) && (n1.key == n2.key);
}
// 更新孩子 diff原理, 目的就是尽量的复用dom  
function updateChildren(oldChildren, children, parent) {
    function createKeyMap(oldChildren) {
        let obj = {};
        // for (let i = 0; i < oldChildren.length; i++) {
        //     let child = oldChildren[i];
        //     if (child) {
        //         console.log(child);
        //         let { key } = child && child.attrs;
        //         if (key) {
        //             obj[key] = i;
        //         }
        //     }
        // }
        oldChildren.forEach((child, index) => {
            let key = child.key;
            if (key) {
                obj[key] = index;
            }
        })
        return obj;
    }


    const map = createKeyMap(oldChildren);
    // 双指针: 旧头 旧尾  新头 新尾
    let oldStartIndex = 0;
    let oldStartVnode = oldChildren[0];
    let oldEndIndex = oldChildren.length - 1;
    let oldEndVnode = oldChildren[oldEndIndex];
    let newStartIndex = 0;
    let newStartVnode = children[0];
    let newEndIndex = children.length - 1;
    let newEndVnode = children[newEndIndex];
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        // 这两个判断是暴力比对的时候可能置空的情况
        if (!oldStartVnode) {
            oldStartVnode = oldChildren[++oldStartIndex];
        } else if (!oldEndVnode) {
            oldEndVnode = oldChildren[--oldEndIndex];
        } else if (isSameVnode(oldStartVnode, newStartVnode)) { // 新头和旧头
            // 如果一样，直接patch即可
            patch(oldStartVnode, newStartVnode);
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = children[++newStartIndex];
        } else if (isSameVnode(oldEndVnode, newEndVnode)) { // 新尾和旧尾
            // 如果一样直接patch即可
            patch(oldEndVnode, newEndVnode);
            oldEndVnode = oldChildren[--oldEndIndex];
            newEndVnode = children[--newEndIndex];
        } else if (isSameVnode(newStartVnode, oldEndVnode)) { // 新头和旧尾
            patch(oldEndVnode, newStartVnode);
            // 如果一样，放在新头的前面
            parent.insertBefore(oldEndVnode.el, oldChildren[oldStartIndex].el);
            newStartVnode = children[++newStartIndex];
            oldEndVnode = oldChildren[--oldEndIndex];
        } else if (isSameVnode(newEndVnode, oldStartVnode)) { // 新尾和旧头
            patch(oldStartVnode, newEndVnode);
            // 如果一样，放到新尾的后面
            parent.insertBefore(oldStartVnode.el, oldChildren[oldEndIndex].el.nextElementSibling);
            newEndVnode = children[--newEndIndex];
            oldStartVnode = oldChildren[++oldStartIndex];
        } else { // 暴力比对
            // 1 创建oldVnode的key的映射表
            // 2 用当前的newStartIndex的key在map中找查找
            let child = children[newStartIndex];
            let newKey = child.attrs.key;
            let index = map[newKey];
            if (index == undefined) { // 没有复用的Key
                // 将当前的child放到oldStartIndex的前面
                parent.insertBefore(createElem(child), oldChildren[oldStartIndex].el);
            } else { // key是复用的
                // 找到一个的可复用的Key,进行patch
                patch(oldChildren[index], child);
                // 获取将要插入到oldStartIndex前的元素
                let oldChild = oldChildren[index].el;
                // 找到了，需要将oldChildren的这项占位置为null, 下次循环就不用再匹配这个vnode了
                oldChildren[index] = null;
                // 插入到oldStartIndex的元素
                parent.insertBefore(oldChild, oldChildren[oldStartIndex].el);
            }
            newStartVnode = children[++newStartIndex];
        }
    }
    if (newStartIndex <= newEndIndex) { // 新的元素多了,插入即可
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            // 兼容从头开始比较和从尾开始比较，如果nextEl不存在，那么insertBefore默认是插入到尾部
            let nextEl = (oldChildren[oldEndIndex + 1] && oldChildren[oldEndIndex + 1].el) || null;
            // parent.appendChild(createElem(children[i]));
            parent.insertBefore(createElem(children[i]), nextEl);
        }
    }
    if (oldStartIndex <= oldEndIndex) {
        for (let i = oldStartIndex; i <= oldEndIndex; i++) {
            let child = oldChildren[i];
            if (child) {
                parent.removeChild(child.el);
            }
        }
    }

}


// 创建组件实例
function createComponent(vnode) {
    let i = vnode.attrs || {};
    // 取出初始化组件实例的方法
    if ((i = i.hook) && (i = i.init)) {
        // 创建组件实例，并将真实节点放到到组件实例的$el，后面会返回
        i(vnode);
        return true;
    }
}

export function createElem(vnode) {
    // 如果是组件，特殊处理
    if (createComponent(vnode)) {
        // 并且返回真实元素，下面会加入到父元素中
        // vnode.componentInstance指向实例的$el
        return vnode.componentInstance.$el;
    }
    // 让el映射到vnode上
    if (vnode.tag) {
        vnode.el = document.createElement(vnode.tag);
        updateProperty(vnode);
        if (vnode.children.length) {
            vnode.children.forEach(child => {
                vnode.el.appendChild(createElem(child));
            })
        }
    } else {
        vnode.el = document.createTextNode(vnode.text);
    }
    return vnode.el;

}

function updateProperty(vnode, oldProps = {}) {
    let attrs = vnode.attrs || {};
    let el = vnode.el;
    let oldStyles = oldProps.style || {};
    let newStyles = attrs.style || {};
    // 老的在新的没有，删除
    for (let prop in oldProps) {
        if (prop === 'style') {
            // style: {color: red}
            // let styles = attrs[prop] || {};
            // let oldStyles = oldProps[prop] || {};
            for (let style in oldStyles) {
                if (!(style in newStyles)) {
                    el.style[style] = '';
                }
            }
        }
        if (!(prop in attrs)) {
            el.removeAttribute(prop);
        }
    }


    // 新的直接放入
    for (let attr in attrs) {
        if (attr === 'style') {
            // let styles = attrs[attr];
            for (let prop in newStyles) {
                if (oldStyles[prop] !== newStyles[prop]) {
                    el.style[prop] = newStyles[prop];
                }
            }
        } else if (attr === 'class') {
            el.className = attrs[attr];
        } else {
            el.setAttribute(attr, attrs[attr]);
        }
    }
}